package com.atguigu.gmall.index.aspect;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.index.annotation.GmallCache;
import com.google.common.base.Charsets;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnel;
import com.google.common.hash.Funnels;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Random;

@Aspect //表示这是一个切点类
@Component  //表示将这个类注入到spring容器
public class GmallCacheAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RBloomFilter bloomFilter;

    /*二、环绕通知
    *  1、方法的返回结果集，必须是Object
    *  2、方法中必须有一个ProceedingJoinPoint对象
    *  3、必须抛出Throwable异常
    *  4、必须手动执行目标方法：joinPoint.proceed(joinPoint.getArgs())
    * */
    @Around("@annotation(com.atguigu.gmall.index.annotation.GmallCache)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{

        //获取目标方法的签名
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        //获取目标方法对象
        Method method = signature.getMethod();
        //获取目标方法上的gmallCache注解对象
        GmallCache gmallCache = method.getAnnotation(GmallCache.class);
        //获取注解中的缓存的前缀
        String prefix = gmallCache.prefix();
        //获取目标方法的参数列表(并以逗号分隔成字符串)
        String args = StringUtils.join(joinPoint.getArgs(), ",");
        //组装缓存的key
        String key = prefix + args;

        //使用布隆过滤器解决缓存穿透,如果没有获取到key，则直接返回null。这样redis就不用缓存那么多数据了
        if (!bloomFilter.contains(key)){
            return null;
        }

        //1、 查询缓存，如果缓存命中，直接返回
        String json = this.redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(json)){
            //如果命中缓存，则将结果反序列化成方法需要的返回值类型之后再返回
            return JSON.parseObject(json,method.getReturnType());
        }

        //2、为了防止缓存击穿，添加分布式锁
        RLock lock = this.redissonClient.getLock(gmallCache.lock() + args);
        lock.lock();

        try {
            //3、在获取分布式锁的过程中，可能有其他请求已经把数据放入缓存，此时再次确认缓存是否存在，存在直接返回
            String json2 = this.redisTemplate.opsForValue().get(key);
            if (StringUtils.isNotBlank(json2)){
                //如果命中缓存，则将结果反序列化成方法需要的返回值类型之后再返回
                return JSON.parseObject(json2,method.getReturnType());
            }
            //4、执行目标方法
            Object result = joinPoint.proceed(joinPoint.getArgs());

            //5、把目标方法的返回结果集放入缓存，并且释放分布式锁（防止缓存雪崩）
            if (result != null){
                int timeout = gmallCache.timeout() + new Random().nextInt(gmallCache.random());
                this.redisTemplate.opsForValue().set(key,JSON.toJSONString(result));
            }

            return result;
        } finally {
            //解锁
            lock.unlock();
        }
    }

    /*一、前置通知
    * 目标方法所在类：joinpoint.getTarget().getClass()
    * 目标方法参数：joinPoint.getArgs()
    * 目标方法：MethodSignature signature = (MethodSignature)joinPoint.getSignature();
    *           signature.getMethod();
     * @Param joinPoint
    * */
    //切点表达式：扫描service包下所有类  JoinPoint:  获取目标方法
    //@Before("execution(* com.atguigu.gmall.index.service.*.*(..))")
    //如果切点在缓存注解里面，那么切点表达式必须写该注解所在位置的全路径
/*    @Before("@annotation(com.atguigu.gmall.index.annotation.GmallCache)")
    public void before(JoinPoint joinPoint){
        System.out.println("前置通知：" + joinPoint.getTarget().getClass().getName());
        System.out.println("参数：" + Arrays.asList(joinPoint.getArgs()));
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();//获取目标方法的签名对象
        Method method = signature.getMethod();  //获取方法对象
        System.out.println("目标方法名：" + method.getName());
    }*/


/*  //测试：使用google的guava方式来实现布隆过滤器
    public static void main(String[] args) {
        //参数1：Funnels.stringFunnel(Charsets.UTF_8)：指定字符编码；参数2：插入的元素数量； 参数3：误判率，默认为0.03，即3%
        BloomFilter<CharSequence> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), 20, 0.3);
        //插入元素
        bloomFilter.put("1");
        bloomFilter.put("2");
        bloomFilter.put("3");
        bloomFilter.put("4");
        bloomFilter.put("5");
        bloomFilter.put("6");
        bloomFilter.put("7");
        //判断是否包含某元素,返回值为true/false
        System.out.println(bloomFilter.mightContain("1"));
        System.out.println(bloomFilter.mightContain("3"));
        System.out.println(bloomFilter.mightContain("5"));
        System.out.println(bloomFilter.mightContain("7"));
        System.out.println(bloomFilter.mightContain("8"));
        System.out.println(bloomFilter.mightContain("9"));
        System.out.println(bloomFilter.mightContain("10"));
        System.out.println(bloomFilter.mightContain("11"));
        System.out.println(bloomFilter.mightContain("12"));
        System.out.println(bloomFilter.mightContain("13"));
        System.out.println(bloomFilter.mightContain("14"));
        System.out.println(bloomFilter.mightContain("15"));
        System.out.println(bloomFilter.mightContain("16"));
        System.out.println(bloomFilter.mightContain("17"));
        System.out.println(bloomFilter.mightContain("18"));
        System.out.println(bloomFilter.mightContain("19"));
    }*/
}
